package bancosys.tec.security.impl.domain;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Comment;
import jmine.tec.persist.annotation.Comments;
import jmine.tec.persist.annotation.Index;
import jmine.tec.persist.annotation.Indexes;
import jmine.tec.persist.schema.api.IndexOrder;

import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Filters;
import org.hibernate.annotations.ForeignKey;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.authorization.annotation.DisplayName;
import bancosys.tec.persist.bussobj.AuthPersistableBusinessObject;
import bancosys.tec.persist.persister.annotation.NaturalKey;

/**
 * Usuário.
 * 
 * @author Marco
 */
@Entity
@Table(name = "USER_SYSTEM")
@Alias("USRSYS")
@SequenceGenerator(name = "SEQ_USRSYS", sequenceName = "SEQ_USRSYS")
@Filters({ @Filter(name = "authFilter", condition = "auth = :auth") })
@NamedQueries({
// recuperação de usuários com User
@NamedQuery(name = "usuario.entityIds", query = "select chineseId.entityId from User usuario join usuario.credentials credential "
        + "join credential.ids chineseId " + "where usuario.id = ? " + "and credential.class = ChineseWallCredential "
        + "and credential.entityName = ? " + "and credential.client.id = ?") })
@DisplayName("Usuário")
@Documentation("ARMAZENA OS USUARIOS QUE ACESSAM O SISTEMA.")
public class User extends AuthPersistableBusinessObject {

    private static final int PASSWORD_LENGHT = 32;

    /**
     * ID do usuário.
     */
    private Long id;

    /**
     * Nome do usuário.
     */
    private String username;

    /**
     * Senha do usuário em MD5.
     */
    private String password;

    /**
     * Credencias atribuídas ao usuário.
     */
    private Set<Credential> credentials;

    /**
     * Grupos aos quais o usuário pertence.
     */
    private Set<Group> groups;

    /**
     * Construtor.
     */
    User() {
        // não faz nada
    }

    /**
     * @return ID do usuário.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_USRSYS")
    @Column(name = "COD_USER", nullable = false)
    @Documentation("CODIGO DO USUARIO.")
    public Long getId() {
        return this.id;
    }

    /**
     * @return nome do usuário.
     */
    @Column(name = "USERNAME", nullable = false, length = 100)
    @DisplayName(value = "Nome de usuário", order = "10")
    @Documentation("NOME DO USUARIO.")
    @NaturalKey
    public String getUsername() {
        return this.username;
    }

    /**
     * @return senha do usuário.
     */
    @Column(name = "PASSWORD", nullable = false, length = PASSWORD_LENGHT)
    @Documentation("SENHA CRIPTOGRAFADA DO USUARIO.")
    public String getPassword() {
        return this.password;
    }

    /**
     * @return credenciais do usuário.
     */
    @ManyToMany(targetEntity = Credential.class, fetch = FetchType.EAGER)
    @JoinTable(name = "ASSOC_USER_CREDENTIAL", joinColumns = { @JoinColumn(name = "COD_USER") }, inverseJoinColumns = { @JoinColumn(name = "COD_CREDENTIAL") })
    @Alias("ASUSCR")
    @ForeignKey(name = "FK_USRSYS_ASUSCR", inverseName = "FK_CREDEN_ASUSCR")
    @org.hibernate.annotations.Fetch(FetchMode.SELECT)
    @DisplayName(value = "Credenciais", order = "11")
    @Comments({ @Comment(table = "ASSOC_USER_CREDENTIAL", value = "ARMAZENA A ASSOCIACAO ENTRE OS USUARIOS E AS CREDENCIAIS."),
            @Comment(table = "ASSOC_USER_CREDENTIAL", column = "COD_CREDENTIAL", value = "CODIGO DA CREDENCIAL"),
            @Comment(table = "ASSOC_USER_CREDENTIAL", column = "COD_USER", value = "CODIGO DO USUARIO.") })
    @Indexes({ @Index(columns = "COD_USER", suffix = "0", orders = IndexOrder.ASC),
            @Index(columns = "COD_CREDENTIAL", suffix = "1", orders = IndexOrder.ASC) })
    public Set<Credential> getCredentials() {
        if (this.credentials == null) {
            this.credentials = new HashSet<Credential>();
        }
        return this.credentials;
    }

    /**
     * @return grupos do usuário
     */
    @ManyToMany(targetEntity = Group.class, fetch = FetchType.EAGER)
    @JoinTable(name = "ASSOC_USER_GROUP", joinColumns = { @JoinColumn(name = "COD_USER") }, inverseJoinColumns = { @JoinColumn(name = "COD_USER_GROUP") })
    @Alias("ASUSGR")
    @ForeignKey(name = "FK_USRSYS_ASUSGR", inverseName = "FK_USRGRO_ASUSGR")
    @org.hibernate.annotations.Fetch(FetchMode.SELECT)
    @DisplayName(value = "Grupos", order = "12")
    @Comments({ @Comment(table = "ASSOC_USER_GROUP", value = "ARMAZENA A ASSOCIACAO ENTRE OS USUARIOS E SEUS GRUPOS."),
            @Comment(table = "ASSOC_USER_GROUP", column = "COD_USER_GROUP", value = "CODIGO DO GRUPO DE USUARIO."),
            @Comment(table = "ASSOC_USER_GROUP", column = "COD_USER", value = "CODIGO DO USUARIO.") })
    @Indexes({ @Index(columns = "COD_USER", suffix = "0", orders = IndexOrder.ASC),
            @Index(columns = "COD_USER_GROUP", suffix = "1", orders = IndexOrder.ASC) })
    public Set<Group> getGroups() {
        if (this.groups == null) {
            this.groups = new HashSet<Group>();
        }
        return this.groups;
    }

    /**
     * Monta um conjunto com as credencias do usuário e também as credenciais dos grupos em que o usuário está.
     * 
     * @return conjunto com as credencias do usuário e também as credenciais dos grupos em que o usuário está.
     */
    @Transient
    public Set<Credential> getAllCredentials() {
        Set<Credential> allCredentials = new HashSet<Credential>();
        allCredentials.addAll(this.getCredentials());
        for (Group group : this.getGroups()) {
            allCredentials.addAll(group.getCredentials());
        }
        return allCredentials;
    }

    /**
     * Define o ID do usuário.
     * 
     * @param id o ID do usuário.
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Define o username do usuário.
     * 
     * @param username o username do usuário.
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * Define a senha do usuário.
     * 
     * @param password a senha do usuário.
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Define as credenciais do usuário.
     * 
     * @param credentials as credenciais do usuário.
     */
    public void setCredentials(Set<Credential> credentials) {
        this.credentials = credentials;
    }

    /**
     * Define os grupos a que o usuário pertence.
     * 
     * @param groups os grupos a que o usuário pertence.
     */
    public void setGroups(Set<Group> groups) {
        this.groups = groups;
    }

    /**
     * Adiciona um grupo a lista de grupos a que o usuário pertence.
     * 
     * @param group o grupo a ser adicionado.
     */
    public void addGroup(Group group) {
        this.getGroups().add(group);
    }
}